package gin

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"github.com/julienschmidt/httprouter"
	"html/template"
	"log"
	"math"
	"net/http"
	"path"
)

const (
	AbortIndex = math.MaxInt8 / 2
)

type (
	// HandlerFunc 中间件函数规范(接收一个上下文对象)
	HandlerFunc func(*Context)

	// H 主要用于响应json
	H map[string]any

	// ErrorMsg 封装错误信息结构体
	ErrorMsg struct {
		Message string `json:"message"`
		Meta    any    `json:"meta"`
	}

	Context struct {
		// 请求
		Req *http.Request
		// 响应
		Writer http.ResponseWriter
		// 用于存储上下文传递信息,如用户认证凭证等
		Keys map[string]any
		// 错误列表
		Errors []ErrorMsg
		// 动态路由参数
		Params httprouter.Params
		// 包含路由组的全局中间件以及用户传入的所有处理函数
		handlers []HandlerFunc
		// 通过路由组传递过来的引擎实例,也就是全局的那个唯一引擎
		engine *Engine
		// 负责记录当前执行函数的索引(防止重复执行),同时用于控制处理函数的数量 128
		index int8
	}

	// RouterGroup 在内部用于配置路由器
	// 1.管理中间件列表
	// 2.借助httprouter实现动态路由
	RouterGroup struct {
		// 处理方法
		Handlers []HandlerFunc
		// 路由组前缀(主要用于路由分组)
		// 整个应用的路由前缀
		prefix string
		// 父级路由
		parent *RouterGroup
		engine *Engine
	}

	// Engine 代表web框架,包装了httprouter和全局中间件列表
	Engine struct {
		*RouterGroup
		// 处理404相关的中间件
		handlers404 []HandlerFunc
		// 初始化httprouter,创建httprouter实例对象
		router        *httprouter.Router
		HTMLTemplates *template.Template
	}
)

// New 返回一个没有附加任何中间件的空白Engine实例
func New() *Engine {
	engine := &Engine{}
	engine.RouterGroup = &RouterGroup{nil, "", nil, engine}
	engine.router = httprouter.New()
	engine.router.NotFound = http.HandlerFunc(engine.handle404)
	return engine
}

// Default 返回一个已使用Recovery和Logger的引擎实例
func Default() *Engine {
	engine := New()
	engine.Use(Recovery(), Logger())
	return engine
}

// LoadHTMLTemplates 初始化HTML模板
func (engine *Engine) LoadHTMLTemplates(pattern string) {
	//ParseGlob根据指定的模式来匹配解析多个模板文件.支持*,?等,如templates/*.tmpl
	//Must 是一个辅助函数，主要用于根据err是否为空来快速触发恐慌中断程序
	// 当未使用{{define}}定义模板名称时,模板名称默认为文件名(去除路径和扩展名后的文件名)
	engine.HTMLTemplates = template.Must(template.ParseGlob(pattern))
}

// NotFound404 添加处理404的方法,默认返回404code
func (engine *Engine) NotFound404(handlers ...HandlerFunc) {
	engine.handlers404 = handlers
}

// handle404 404处理是一个单独的上下文
func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
	// 将用户自定义的404中间件添加至洋葱链中
	handlers := engine.combineHandlers(engine.handlers404)
	// 创建一个新的上下文对象
	c := engine.createContext(w, req, nil, handlers)
	// 如果没有404处理逻辑则用默认的http404处理
	if engine.handlers404 == nil {
		http.NotFound(c.Writer, c.Req)
	} else {
		// 否则使用自己的
		c.Writer.WriteHeader(404)
	}
	// 控制权转让出去,执行其他中间件
	c.Next()
}

// Use 向中间件列表中追加一个中间件函数
func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
	group.Handlers = append(group.Handlers, middlewares...)
}

// ServeFiles 用于从执行的文件系统根目录提供文件服务
// path 比如以/*filepath结尾
// 如/static/*filepath 这样文件将从本地路径/static/*filepath提供文件服务
// 这里的/static 就是root参数所指定的根目录
// 示例:router.ServeFiles("/src/*filepath", http.Dir("/var/www"))
// 此示例表明当客户端请求以"/src/"开头的路径时,服务器会从"/var/www"目录中查找对应的文件
func (engine *Engine) ServeFiles(path string, root http.FileSystem) {
	engine.router.ServeFiles(path, root)
}

func (engine *Engine) Run(addr string) {
	http.ListenAndServe(addr, engine)
}

// ServeHTTP 使路由器实现http.Handler接口
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// 借助httprouter.ServeHTTP处理请求路径,匹配路由,处理路由参数等
	// 也就是这一步替换了原生http包的路由处理方式
	engine.router.ServeHTTP(w, req)
}

/************************************/
/********** ROUTES GROUPING *********/
/************************************/

func (group *RouterGroup) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context {
	return &Context{
		Writer:   w,
		Req:      req,
		index:    -1,
		engine:   group.engine,
		Params:   params,
		handlers: handlers,
	}
}

func (group *RouterGroup) Group(component string, handlers ...HandlerFunc) *RouterGroup {
	// 指定组的路由前缀
	prefix := path.Join(group.prefix, component)
	return &RouterGroup{
		// 这里调用combineHandlers将路由组的中间件组合起来，以达到针对于不同的路由组的中间件
		Handlers: group.combineHandlers(handlers),
		parent:   group,
		prefix:   prefix,
		engine:   group.engine,
	}
}

// Handle 使用给定的路径和方法注册一个新的请求句柄和中间件
// 最后一个处理程序应该是真正的处理程序,其他的应该是可以并且能在不同路由之间共享的中间件
// 此函数旨在批量加载并允许使用更少的常用的,非标准化的或自定义的方法
// Handle方法一结束里面的handlers就会清空(除了全局中间件)
func (group *RouterGroup) Handle(method, p string, handlers []HandlerFunc) {
	p = path.Join(group.prefix, p)
	//fmt.Println("前缀:", group.prefix)
	//fmt.Println("路径:", p)

	// 将路由组的全局中间件和用户传入的处理函数合并成一个函数切片,按顺序合并,全局中间件在前,用户处理函数在后
	handlers = group.combineHandlers(handlers)

	// 借助httprouter的路由处理函数来处理不同请求方式的请求,并且支持动态路由
	group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
		// 创建一个新的上下文对象,并通过Next依次调用中间件函数和句柄函数,当前版本同时最多支持128个处理函数
		group.createContext(w, req, params, handlers).Next()
	})
}

// GET 路由器的快捷方式. Handle（"GET"， path，句柄）
func (group *RouterGroup) GET(path string, handlers ...HandlerFunc) {
	group.Handle("GET", path, handlers)
}

// POST 路由器的快捷方式. Handle（"POST"， path，句柄）
// POST请求不具有幂等性，多次相同的post请求会创建多个相同的资源
func (group *RouterGroup) POST(path string, handlers ...HandlerFunc) {
	group.Handle("POST", path, handlers)
}

// DELETE 路由器的快捷方式. Handle（"DELETE"， path，句柄）
// 通常具有幂等性
func (group *RouterGroup) DELETE(path string, handlers ...HandlerFunc) {
	group.Handle("DELETE", path, handlers)
}

// PATCH 路由器的快捷方式. Handle（"PATCH"， path，句柄）
// 对服务器的资源进行部分更新,与put不同的是，put通常是替换整个资源
// 不具备幂等性,每次更新可能会产生不同的结果,比如只更新用户的电子邮箱
func (group *RouterGroup) PATCH(path string, handlers ...HandlerFunc) {
	group.Handle("PATCH", path, handlers)
}

// PUT 路由器的快捷方式. Handle（"PUT"， path，句柄）
// put请求常用于更新服务器上的资源,通常具有幂等性,多次执行相同的put所产生的效果是相同的
// 比如更新整个用户的信息
func (group *RouterGroup) PUT(path string, handlers ...HandlerFunc) {
	group.Handle("PUT", path, handlers)
}

// combineHandlers 将中间件函数和句柄函数切片合并为一个处理函数切片
func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc {
	// 根据中间件的函数以及真正要执行的程序函数创建一个合适容量的处理函数切片
	s := len(group.Handlers) + len(handlers)
	// 创建一个s容量的切片
	h := make([]HandlerFunc, 0, s)
	// 添加中间件函数
	h = append(h, group.Handlers...)
	// 添加句柄函数
	h = append(h, handlers...)
	// 返回处理函数切片
	return h

} /************************************/
/****** FLOW AND ERROR MANAGEMENT****/
/************************************/

// Next 应该只在中间件中使用
// 它执行调用处理程序内部链中的挂起处理程序
// 简单来讲就是循环执行所有中间件切片
func (c *Context) Next() {
	// 记录当前正在执行的处理函数的索引,最开始为-1
	// 顺便起到控制的作用,以防止重复执行
	// 因为index属于Context,因此只要Context不是新的,那么index就会持续保持计数
	c.index++
	// 控制处理函数的数量,最多128个,程序不会出错,但会执行若干个函数(由转换后的数字决定,但不会超过127,从0开始)
	s := int8(len(c.handlers))
	fmt.Println("当前上下文总处理函数数量:", s)
	// 循环依次处理,最多处理128个处理函数,每次都将上下文传递给下一个处理函数
	// 每循环一次index+1,意味着执行过了一个处理函数
	// 下次处理是就可以接着上一次index的位置继续处理
	for ; c.index < s; c.index++ {
		fmt.Println("正在依次执行处理函数(包括中间件和处理函数()):", c.index)
		c.handlers[c.index](c)
	}
}

// Abort 强制系统不要继续调用挂起的处理程序
// 如:第一个处理程序检查请求是否被授权,如果不是,则应调用context.Abort(401)
// 永远不会为该请求调用其余的挂起处理程序
func (c *Context) Abort(code int) {
	c.Writer.WriteHeader(code)
	// 直接修改index为63,这样如果总处理函数没有超过63,那么则不会执行其他挂起函数
	c.index = AbortIndex
}

// Fail 停止处理并返回错误
func (c *Context) Fail(code int, err error) {
	// 添加操作终端错误信息到错误列表
	c.Error(err, "Operation aborted")
	c.Abort(code)
}

// Error 将错误附加到当前上下文。错误则被推送到错误列表中
func (c *Context) Error(err error, meta any) {
	c.Errors = append(c.Errors, ErrorMsg{
		Message: err.Error(),
		Meta:    meta,
	})
}

/************************************/
/******** METADATA MANAGEMENT********/
/************************************/

// Set 为指定的上下文设置一个新的键值对
// 延迟初始化hashmap(懒加载)
func (c *Context) Set(key string, item any) {
	if c.Keys == nil {
		c.Keys = make(map[string]any)
	}
	c.Keys[key] = item
}

// Get 返回给定键的值
// 如果值不存在，则会触发panic
func (c *Context) Get(key string) any {
	var ok bool
	var item any
	if c.Keys != nil {
		item, ok = c.Keys[key]
	} else {
		item, ok = nil, false
	}
	if !ok || item == nil {
		log.Panicf("Key %s doesn't exist", key)
	}
	return item
}

/************************************/
/******** ENCOGING MANAGEMENT********/
/************************************/

func (c *Context) EnsureBody(item any) bool {
	// 如果校验不通过,中断后续请求
	if err := c.ParseBody(item); err != nil {
		c.Fail(400, err)
		return false
	}
	return true
}

// ParseBody 将消息正文内容解析为Json输入，将json有效payload解码为指定的指针结构
func (c *Context) ParseBody(item any) error {
	decoder := json.NewDecoder(c.Req.Body)
	// 尝试从请求体中读取json数据，并将它解析到item变量里
	if err := decoder.Decode(&item); err == nil {
		// 校验必须字段
		return Validate(c, item)
	} else {
		return err
	}
}

func (c *Context) String(code int, msg string) {
	c.Writer.Header().Set("Content-Type", "text/plain")
	c.Writer.WriteHeader(code)
	c.Writer.Write([]byte(msg))
}

// JSON 以json格式响应客户端
func (c *Context) JSON(code int, obj any) {
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	c.Writer.Header().Set("Content-Type", "application/json")
	// 将go的数据结构编码为JSON格式，并将编码后的JSON数据写入c.Writer到输出中
	encoder := json.NewEncoder(c.Writer)

	// 将obj编译Json串
	if err := encoder.Encode(obj); err != nil {
		// 如果出现错误,将错误信息添加到错误列表中
		c.Error(err, obj)
		// 并返回给客户端一个错误响应
		http.Error(c.Writer, err.Error(), 500)
	}
}

// XML 将特定结构体作为XML序列化到响应正文中
//
//	结构体如: type User struct {
//			XMLName xml.Name `xml:"user"`
//	 	Name string `xml:"name"`
//	     Age uint8  `xml:"age"`
//		}
func (c *Context) XML(code int, obj any) {
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	c.Writer.Header().Set("Content-Type", "application/xml")
	// 创建一个XML编码器,编码后的xml数据会被写入到c.Writer中
	encoder := xml.NewEncoder(c.Writer)
	// 将obj编译为XML串,写入到c.Writer中
	if err := encoder.Encode(obj); err != nil {
		c.Error(err, obj)
		http.Error(c.Writer, err.Error(), 500)
	}
}

// HTML 响应由name指定的http模板
// 更多的见http://golang.org/doc/articles/wiki/
func (c *Context) HTML(code int, name string, data any) {
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	c.Writer.Header().Set("Content-Type", "text/html")
	// c.Writer渲染后模板内容会被写入到这里
	// name 要执行的模板名称，在模板集合中每个模板都有一个唯一的名字(如未指定{{define}}则默认为文件名)
	// data 传递给模板的数据
	if err := c.engine.HTMLTemplates.ExecuteTemplate(c.Writer, name, data); err != nil {
		c.Error(err, map[string]interface{}{
			"name": name,
			"data": data,
		})
		http.Error(c.Writer, err.Error(), 500)
	}
}

// Data 将一些数据写入正文流并更新HTTP状态码
func (c *Context) Data(code int, data []byte) {
	c.Writer.WriteHeader(code)
	c.Writer.Write(data)
}
